﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;

using DigitalQuran.Extensions;
using System.Collections.ObjectModel;

namespace DigitalQuran.Numerology
{
    public static class Numbers
    {
        /// <summary>
        /// Gets the Maximum number to deal with in Research Edition
        /// </summary>
        public const int MaxOfMaxNumber = 8388607;

        private const int FibonaccisLimit = 100;        // F99 = 3736710778780434371
        private const int Log2Limit = 63;               // 2^0 .. 2^63 - after that it is -ve
        private const int Log10Limit = 19;              // 10^0 .. 10^18 - after that it is -ve

        /// <summary>
        /// Value of PI = circumference / diameter ~= 355/113
        /// </summary>
        public const double PI = 3.141592653589793238462643383279D;

        /// <summary>
        /// Value of E = Euler's number = 0SUM∞(1/n!)
        /// </summary>
        public const double E = 2.718281828459045235360287471352D;

        /// <summary>
        /// Value of phi = (sqrt(5)+1)/2
        /// </summary>
        public const double PHI = 1.618033988749894848204586834365D;

        static int PrimesCount = 564163;                // Primes to load
        static int AdditivePrimesCount = 155961;        // Additive Primes to load
        static int PurePrimesCount = 23;                // Pure Primes to load

        static int CompositeCount = 7824443;            // Composite numbers to load
        static int AdditiveCompositeCount = 5901688;    // Additive Composites to load
        static int PureCompositeCount = 143602;         // Pusre Composites to load

        static bool isOnePrime = true;
        /// <summary>
        /// Gets Sets wether to deal One as prime or not
        /// </summary>
        public static bool IsOnePrime { get { return isOnePrime; }
            set {
                if (isOnePrime != value) {
                    isOnePrime = value;

                    foreach (var file in Directory.GetFiles(DigitalQuranDirectories.NumbersLocal)) {
                        File.Delete(file);
                    }

                    WritePrimes();
                    WriteComposites();
                }
            }
        }

        static Numbers()
        {
            // Important to Update Count Information to properly load according to Edition
            switch (Globals.Edition)
            {
                case Edition.Research:
                    PrimesCount = 564163;
                    AdditivePrimesCount = 155961;
                    PurePrimesCount = 23;

                    CompositeCount = 7824443;
                    AdditiveCompositeCount = 5901688;
                    PureCompositeCount = 143602;
                    break;

                case Edition.Standard:
                case Edition.Grammar:
                    PrimesCount = 155611;
                    AdditivePrimesCount = 42987;
                    PurePrimesCount = 22;

                    CompositeCount = 1941539;
                    AdditiveCompositeCount = 1460586;
                    PureCompositeCount = 11604;
                    break;

                case Edition.Lite:
                    PrimesCount = 43390;
                    AdditivePrimesCount = 12207;
                    PurePrimesCount = 19;

                    CompositeCount = 480896;
                    AdditiveCompositeCount = 360853;
                    PureCompositeCount = 16178;
                    break;
            }

            // By-default 1 is dealed as prime
            IsOnePrime = true;
        }

        #region Binary File IO

        static bool ReadBinaryFile(string path, ref long[] container, int max)
        {
            container = new long[0];

            if (File.Exists(path))
            {
                using (var stream = new FileStream(path, FileMode.Open))
                using (var reader = new BinaryReader(stream))
                {
                    container = new long[max];
                    for (int i = 0; i < max; i++)
                        container[i] = reader.ReadInt64();
                }

                return true;
            }

            return false;
        }

        static void WriteBinaryFile(string path, long[] container)
        {
            using (var stream = new FileStream(path, FileMode.Create))
            using (var writer = new BinaryWriter(stream))
            {
                int max = container.Length;
                for (int i = 0; i < max; i++)
                    writer.Write(container[i]);
            }
        }

        #endregion

        private static int MaxNumber =
            int.MaxValue /
            (
                (Globals.Edition == Edition.Lite) ? (4096) :
                (Globals.Edition == Edition.Standard) ? (1024) :
                (Globals.Edition == Edition.Grammar) ? (1024) :
                (Globals.Edition == Edition.Research) ? (256) : 1024
            );

        #region Math Constants

        static byte[] piDigits = null;
        /// <summary>
        /// Gets digits of PI Value
        /// </summary>
        public static byte[] PiDigits
        {
            get
            {
                if (piDigits == null)
                    ReadPiDigits();

                return piDigits;
            }
        }

        static byte[] eDigits = null;
        /// <summary>
        /// Gets digits of value of E
        /// </summary>
        public static byte[] EDigits
        {
            get
            {
                if (eDigits == null)
                    ReadEDigits();

                return eDigits;
            }
        }

        static byte[] phiDigits = null;
        /// <summary>
        /// Gets 100 digits of Phi Value
        /// </summary>
        public static byte[] PhiDigits
        {
            get
            {
                if (phiDigits == null)
                    ReadPhiDigits();

                return phiDigits;
            }
        }

        /// <summary>
        /// Reads digits of PI from file
        /// </summary>
        private static void ReadPiDigits()
        {
            string filename = DigitalQuranDirectories.Numbers + "pi.bin";

            if (File.Exists(filename))
            {
                using (var stream = new FileStream(filename, FileMode.Open))
                using (var reader = new BinaryReader(stream))
                {
                    
                    piDigits = new byte[102399];
                    for (int i = 0; i < 102399; i++)
                    {
                        piDigits[i] = reader.ReadByte();
                    }                        
                }                
            }
        }

        private static void ReadEDigits()
        {
            string filename = DigitalQuranDirectories.Numbers + "e.bin";

            if (File.Exists(filename))
            {
                using (var reader = new BinaryReader(new FileStream(filename, FileMode.Open)))
                {
                    eDigits = new byte[102399];
                    for (int i = 0; i < 102399; i++)                        
                        eDigits[i] = reader.ReadByte();                        
                }                
            }
        }

        private static void ReadPhiDigits()
        {
            string filename = DigitalQuranDirectories.Numbers + "phi.bin";

            if (File.Exists(filename))
            {
                using (var reader = new BinaryReader(new FileStream(filename, FileMode.Open)))
                {
                    phiDigits = new byte[102399];
                    for (int i = 0; i < 102399; i++)
                        phiDigits[i] = reader.ReadByte();
                }
            }
        }
        #endregion

        #region Prime Numbers

        static long[] _Primes = null;
        /// <summary>
        /// Gets the list of prime numbers for current edition
        /// </summary>
        public static long[] Primes
        {
            get
            {
                if (_Primes == null)
                    ReadPrimes();

                return _Primes;
            }
        }

        static long[] _AdditivePrimes = null;
        /// <summary>
        /// Gets list of Additive Prime Numbers for current edition
        /// </summary>
        public static long[] AdditivePrimes
        {
            get
            {
                if (_AdditivePrimes == null)
                    ReadAdditivePrimes();

                return _AdditivePrimes;
            }
        }

        static long[] _PurePrimes = null;
        /// <summary>
        /// Gets list of Pure Primes for current edition
        /// </summary>
        public static long[] PurePrimes
        {
            get
            {
                if (_PurePrimes == null)
                    ReadPurePrimes();

                return _PurePrimes;
            }
        }

        /// <summary>
        /// Counts primes numbers upton the limit including limit
        /// </summary>
        /// <param name="limit">Limit of number upto which to calculate primes</param>
        /// <returns>Return numbers of primes from 1 to limit</returns>
        public static int CountPrimes(long limit)
        {
            int result = 0;
            for (long i = 1; i <= limit; i++)
            {
                if (i.IsPrime())
                    result++;              
            }

            return result;
        }

        /// <summary>
        /// Counts Additive primes upto the limit including limit
        /// </summary>
        /// <param name="limit">Limit of number upto which to calculate additive primes</param>
        /// <returns>Return numbers of additive primes from 1 to limit</returns>
        public static int CountAdditivePrimes(long limit)
        {
            int result = 0;
            for (long i = 1; i <= limit; i++)
            {
                if (i.IsAdditivePrime())                
                    result++;                
            }

            return result;
        }

        /// <summary>
        /// Counts Pure Primes upto the limit including limit
        /// </summary>
        /// <param name="limit">Limit of number upto which to calculate pure primes</param>
        /// <returns>Return numbers of pure primes from 1 to limit</returns>
        private static int CountPurePrimes(long limit)
        {
            int result = 0;
            for (long i = 0; i <= limit; i++)
            {
                if (i.IsPurePrime())
                    result++;
            }

            return result;
        }

        /// <summary>
        /// Check if three numbers are additive primes and their L2R and R2L concatinations are additive primes too.
        /// <para>Example:</para>
        /// <para>Quran chapter The Key has:</para>
        /// <para>(7, 29, 139) are primes with primes digit sums (7=7, 2+9=11, 1+3+9=13)</para>
        /// <para>and 729139, 139297 primes with prime digit sum (1+3+9+2+9+7=31)</para>
        /// </summary>
        /// <param name="n1">1st Number</param>
        /// <param name="n2">2nd Number</param>
        /// <param name="n3">3rd Number</param>
        /// <returns></returns>
        public static bool ArePrimeTriplets(long n1, long n2, long n3)
        {
            if (n1.IsAdditivePrime() &&
                n2.IsAdditivePrime() &&
                n3.IsAdditivePrime())
            {
                try
                {
                    long l2r = n1.RightJoin(n2).RightJoin(n3);
                    long r2l = n3.RightJoin(n2).RightJoin(n1);

                    return (l2r.IsAdditivePrime() &&
                            r2l.IsAdditivePrime());
                }
                catch { }
            }

            return false;
        }

        public static List<int> SieveOfEratosthenes(int limit)
        {
            // guard against parameter out of range
            if ((limit < 2) || (limit > (int)(int.MaxValue * 0.9999999)))
            {
                return new List<int>(0);
            }

            // Legendre's constant to approximate the number of primes below N
            int maxPrimes = (int)Math.Ceiling((limit / (Math.Log(limit) - 1.08366)));
            if (maxPrimes < 1)
            {
                maxPrimes = 1;
            }
            List<int> primes = new List<int>(maxPrimes);

            // bit array to cross out multiples of primes successively
            // from N^2, jumping 2N at a time (to skip even multiples)
            BitArray candidates = new BitArray(limit + 1, true);

            // add number 2 as prime
            primes.Add(2);
            
            // get the ceiling of sqrt of N
            var sqrtOfLimit = (int)Math.Ceiling(Math.Sqrt(limit));

            // start from 3 and skip even numbers
            // don't go beyond limit or overflow into negative
            for (int i = 3; (i > 0 && i <= limit); i += 2)
            {
                // if not-crossed out candidate yet
                if (candidates[i])
                {
                    // add candidate
                    primes.Add(i);

                    // upto the sqrt of N
                    if (i <= sqrtOfLimit)
                    {
                        // and cross out non-even multiples from i*i and skip even i multiples
                        // don't go beyond limit, or overflow into negative
                        for (int j = i * i; (j > 0 && j <= limit); j += 2 * i)
                            candidates[j] = false;
                    }
                }
            }
            return primes;
        }

        /// <summary>
        /// Writes Prime, Additive Prime, Pure Prime to disk
        /// </summary>
        static void WritePrimes()
        {
            BitArray composites = new BitArray(MaxOfMaxNumber + 1);
            var primes = new List<long>(PrimesCount);

            if (IsOnePrime)
                primes.Add(1);

            primes.Add(2);

            // process odd numbers only 3, 5, 7, 9, 11, ...
            var sqrt = (long)Math.Sqrt(MaxOfMaxNumber) + 1;
            for (int i = 3; i <= MaxOfMaxNumber; i += 2)
            {
                if (!composites[(int)i])
                {
                    primes.Add(i);

                    // mark off multiples of i starting from i*i and skipping even "i"s
                    if (i < sqrt)
                        for (int j = i * i; j <= MaxOfMaxNumber; j += 2 * i)
                            composites[(int)j] = true;
                }
            }

            WriteBinaryFile(DigitalQuranDirectories.NumbersLocal + "primes.bin", primes.ToArray());

            if (_Primes == null || _Primes.Length == 0) {
                _Primes = new long[PrimesCount];
                primes.CopyTo(0, _Primes, 0, PrimesCount);
            }

            int max = primes.Count;
            var additivePrimes = new List<long>(AdditivePrimesCount);
            for (int i = 0; i < max; i++)
            {
                // Check digit sum of prime numbers only
                if (primes[i].DigitSum().IsPrime())
                {
                    additivePrimes.Add(primes[i]);
                }
            }

            WriteBinaryFile(DigitalQuranDirectories.NumbersLocal + "additive-primes.bin", additivePrimes.ToArray());

            if (_AdditivePrimes == null || _AdditivePrimes.Length == 0) {
                _AdditivePrimes = new long[AdditivePrimesCount];
                additivePrimes.CopyTo(0, _AdditivePrimes, 0, AdditivePrimesCount);
            }

            max = additivePrimes.Count;
            var purePrimes = new List<long>(PurePrimesCount);
            for (int i = 0; i < max; i++)
            {
                // Check only additive primes for pure primes
                if (additivePrimes[i].IsPrimeDigits())                
                    purePrimes.Add(additivePrimes[i]);                
            }

            WriteBinaryFile(DigitalQuranDirectories.NumbersLocal + "pure-primes.bin", purePrimes.ToArray());

            if (_PurePrimes == null || _PurePrimes.Length == 0) {
                _PurePrimes = new long[PurePrimesCount];
                purePrimes.CopyTo(0, _PurePrimes, 0, PurePrimesCount);
            }

            purePrimes.Clear();
            additivePrimes.Clear();
            primes.Clear();            
        }

        /// <summary>
        /// Reads Primes from disk
        /// </summary>
        static void ReadPrimes()
        {
            if (_Primes == null)
            {
                if (!ReadBinaryFile(DigitalQuranDirectories.NumbersLocal + "primes.bin", ref _Primes, PrimesCount)) {
                    WritePrimes();
                }
            }
        }

        /// <summary>
        /// Read Additive Primes from disk
        /// </summary>
        static void ReadAdditivePrimes()
        {
            if (_AdditivePrimes == null)
            {
                if (!ReadBinaryFile(DigitalQuranDirectories.NumbersLocal + "additive-primes.bin", ref _AdditivePrimes, AdditivePrimesCount))
                    WritePrimes();
            }
        }

        /// <summary>
        /// Read Pure Primes from disk
        /// </summary>
        static void ReadPurePrimes()
        {
            if (_PurePrimes == null)
            {
                if (!ReadBinaryFile(DigitalQuranDirectories.NumbersLocal + "pure-primes.bin", ref _PurePrimes, PurePrimesCount))
                    WritePrimes();
            }
        }

        #endregion

        #region Composite
        
        private static long[] _AdditiveComposites = null;
        private static long[] _PureComposites = null;

        static long[] _Composites = null;
        /// <summary>
        /// Gets array of composite number for current edition
        /// </summary>
        public static long[] Composites
        {
            get
            {
                if (_Composites == null)                
                    ReadComposites();
                
                return _Composites;
            }
        }

        /// <summary>
        /// Gets array of Additive Primes for current edition
        /// </summary>
        public static long[] AdditiveComposites
        {
            get
            {
                if (_AdditiveComposites == null)
                    ReadAdditiveComposites();
                
                return _AdditiveComposites;
            }
        }

        /// <summary>
        /// Gets array of pure primes for current edition
        /// </summary>
        public static long[] PureComposites
        {
            get
            {
                if (_PureComposites == null)
                    GeneratePureComposites();                

                return _PureComposites;
            }
        }

        /// <summary>
        /// Writes Composite, Additive Composite, Pure Composites to disk
        /// </summary>
        static void WriteComposites()
        {
            BitArray compFlag = new BitArray(MaxOfMaxNumber + 1);

            var composites = new List<long>(CompositeCount);

            for (int i = 4; i <= MaxOfMaxNumber; i += 2)
            {
                compFlag[i] = true;
            }

            // process odd numbers // 3, 5, 7, 9, 11, ...
            var sqrt = (long)Math.Sqrt(MaxOfMaxNumber) + 1;
            for (int i = 3; i <= MaxOfMaxNumber; i += 2)
            {
                if (!compFlag[i])
                {
                    // mark off multiples of i
                    if (i <= sqrt)
                    {
                        for (int j = i * i; j <= MaxOfMaxNumber; j += 2 * i)                        
                            compFlag[j] = true;                        
                    }
                }
            }

            for (int i = 4; i <= MaxOfMaxNumber; i++)
            {
                if (compFlag[(int)i])
                    composites.Add(i);
            }

            WriteBinaryFile(DigitalQuranDirectories.NumbersLocal + "composites.bin", composites.ToArray());

            if (_Composites == null || _Composites.Length == 0) {
                _Composites = new long[CompositeCount];
                composites.CopyTo(0, _Composites, 0, CompositeCount);
            }

            int max = composites.Count;
            var additiveComposites = new List<long>(AdditivePrimesCount);
            for (int i = 0; i < max; i++)
            {
                if (composites[i].DigitSum().IsComposite())
                    additiveComposites.Add(composites[i]);
            }

            WriteBinaryFile(DigitalQuranDirectories.NumbersLocal + "additive-composites.bin", additiveComposites.ToArray());

            if (_AdditiveComposites == null || _AdditiveComposites.Length == 0) {
                _AdditiveComposites = new long[AdditiveCompositeCount];
                additiveComposites.CopyTo(0, _AdditiveComposites, 0, AdditiveCompositeCount);
            }

            max = additiveComposites.Count;
            var pureComposites = new List<long>(PureCompositeCount);
            for (int i = 0; i < max; i++)
            {
                if (additiveComposites[i].IsCompositeDigits())                
                    pureComposites.Add(additiveComposites[i]);                
            }

            WriteBinaryFile(DigitalQuranDirectories.NumbersLocal + "pure-composites.bin", pureComposites.ToArray());

            if (_PureComposites == null || _PureComposites.Length == 0) {
                _PureComposites = new long[PureCompositeCount];
                pureComposites.CopyTo(0, _PureComposites, 0, PureCompositeCount);
            }
        }

        /// <summary>
        /// Reads Composite numbers from disk
        /// </summary>
        private static void ReadComposites()
        {
            if (_Composites == null)
            {
                if (!ReadBinaryFile(DigitalQuranDirectories.NumbersLocal + "composites.bin", ref _Composites, CompositeCount))
                    WriteComposites();
            }
        }

        /// <summary>
        /// Read Additive Composites from disk
        /// </summary>
        private static void ReadAdditiveComposites()
        {
            if (_AdditiveComposites == null)
            {
                if (!ReadBinaryFile(DigitalQuranDirectories.NumbersLocal + "additive-composites.bin", ref _AdditiveComposites, AdditiveCompositeCount))
                    WriteComposites();
            }
        }

        /// <summary>
        /// Read Pure Composites from disk
        /// </summary>
        private static void GeneratePureComposites()
        {
            if (_PureComposites == null)
            {
                if (!ReadBinaryFile(DigitalQuranDirectories.NumbersLocal + "pure-composites.bin", ref _PureComposites, PureCompositeCount))
                    WriteComposites();
            }
        }

        #endregion

        #region Mersenne Primes

        static long[] _MersennePrimes = null;
        /// <summary>
        /// Gets Mersenne Primes array
        /// </summary>
        public static long[] MersennePrimes
        {
            get
            {
                if (_MersennePrimes == null)                
                    ReadMersennePrimes();
                
                return _MersennePrimes;
            }
        }

        /// <summary>
        /// Raeds Mersenne Primes from disk
        /// </summary>
        static void ReadMersennePrimes()
        {
            ReadBinaryFile(DigitalQuranDirectories.Numbers + "mersenne-primes.bin", ref _MersennePrimes, 49);
        }

        #endregion

        #region Fabnocii
        private static List<long> _Fibonaccis = null;
        /// <summary>
        /// Gets the Fanbnocci Series upto
        /// </summary>
        public static List<long> Fibonaccis
        {
            get
            {
                if (_Fibonaccis == null)
                    GenerateFibonaccis();
                
                return _Fibonaccis;
            }
        }

        /// <summary>
        /// Generate Fabnicii Series
        /// </summary>
        private static void GenerateFibonaccis()
        {
            _Fibonaccis = new List<long>(FibonaccisLimit);

            _Fibonaccis.Add(1); // 0th item
            _Fibonaccis.Add(1); // 1st item

            for (int i = 2; i < FibonaccisLimit; i++)            
                _Fibonaccis.Add(_Fibonaccis[i - 1] + _Fibonaccis[i - 2]);            
        }

        #endregion

        #region Log 2
        static List<long> _Log2s = null;
        /// <summary>
        /// Gets the Log 2 Series
        /// </summary>
        public static List<long> Log2s
        {
            get
            {
                if (_Log2s == null)                
                    GenerateLog2s();
                
                return _Log2s;
            }
        }

        private static void GenerateLog2s()
        {
            _Log2s = new List<long>(Log2Limit);
            for (int i = 0; i < Log2Limit; i++)            
                _Log2s.Add((long)Math.Pow(2, i));   
        }

        #endregion

        #region Log 10
        static List<long> _Log10s = null;
        /// <summary>
        /// Gets Log 10 Series
        /// </summary>
        public static List<long> Log10s
        {
            get
            {
                if (_Log10s == null)                
                    GenerateLog10s();
               
                return _Log10s;
            }
        }

        private static void GenerateLog10s()
        {
            _Log10s = new List<long>(Log10Limit);
            for (int i = 0; i < Log10Limit; i++)
            {
                _Log10s.Add((long)Math.Pow(10, i));
            }
        }

        #endregion
    }
}